package cards;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * Class Hand represents cards in a player's hand
 * @author Vlada
 */
public class Hand {

    /** One array list for all suits -> possibly better to use 
     *      for each suit it's own array list
    */    
    /** List of cards in a player's hand **/
    private ArrayList<Card> cardsInTheHand;
    
    /** Map of indexes of the first card for each suit
     *    - used for fast access to the suits
     **/
    private Map<Suit, Integer> indexOfSuits = new HashMap<>(Suit.values().length);
    
    /** Number of cards from each suit **/
    private Map<Suit, Integer> numberOfCardsPerSuit = new HashMap<>(Suit.values().length);
    
    public Hand(int numberOfCards) {
        this.cardsInTheHand = new ArrayList(numberOfCards);
        for (Suit s : Suit.values()) {
            /** Initialize the indexes for suits **/
            indexOfSuits.put(s, -1);
            numberOfCardsPerSuit.put(s, 0);
        }        
    }
    
    /**
     * Returns the card on the given index
     * @param index
     * @return card
     * Throws IndexOutOfBoundException if (index >= number of cards in the hand
     *                                   || index < 0)
     */
    public Card getCard(int index) throws IndexOutOfBoundsException {
        return cardsInTheHand.get(index);
    }
    
    /**
     * Removes the card on the given index
     * @param index
     * @return the removed card
     * Throws IndexOutOfBoundException if (index >= number of cards in the hand
     *                                   || index < 0)
     */    
    public Card removeCard(int index) throws IndexOutOfBoundsException {
        //TODO - update the indexes and number of cards / suit
        Card removedCard = cardsInTheHand.remove(index);
        Suit removedCardSuit = removedCard.getSuit();        
        // 1) Update counter for the removed card's suit
        numberOfCardsPerSuit.put(removedCardSuit, numberOfCardsPerSuit.get(removedCardSuit) + 1);        
        // 2) Update the other suits' indexes
        updateSuitIndexes(false, index);
        
        return removedCard;
        
    }
    
    /**
     * Adds the card to appropriate place in the hand
     * Cards are sorted 
     * @param newCard 
     */
    public void addCard(Card newCard) {
        Suit cardSuit = newCard.getSuit();
        //Number of cards of the suit
        int numberOfCards = numberOfCardsPerSuit.get(cardSuit);
        
        /** If Suit not in the hand yet **/
        //Put the new card in the end of the list
        //  & update index and number of cards
        if (numberOfCards == 0) {
            cardsInTheHand.add(newCard);
            indexOfSuits.put( cardSuit, cardsInTheHand.size()-1 );
            numberOfCardsPerSuit.put(cardSuit, 1);
        } else {
        /** If Suit already in the hand **/
            // 1) Get the index of the suit and put the card on the correct place
            // (Cards in the hand sorted from the lowest rank to the highest
            //TODO !! make clear and easy readable code!!
            for (int i = indexOfSuits.get(cardSuit); 
                    i < indexOfSuits.get(cardSuit) + numberOfCards; i++) {
                if (newCard.getRank().compareTo(cardsInTheHand.get(i).getRank()) < 0
                        || (i + 1 == indexOfSuits.get(cardSuit) + numberOfCards)) {
                    cardsInTheHand.add(i, newCard);
                    break;
                }
            }
            //Increment the counters of cards in the suit
            numberOfCardsPerSuit.put(cardSuit, numberOfCards++);
            // 2) Update (increment) the suits' indexes if necessary
            //      (if the index is higher than the new card's suit index)
            updateSuitIndexes(true, indexOfSuits.get(cardSuit));
            for (Suit s : Suit.values()) {
                int ind = indexOfSuits.get(s);
                if (ind > indexOfSuits.get(cardSuit)) {                    
                    indexOfSuits.put(s, ind++);
                }
            }
        }
        
    }
    
    /**
     * Returns true if there are no more cards left in hand.
     *  Otherwise returns false.
     * @return boolean
     */
    public boolean isEmpty() {
        return cardsInTheHand.isEmpty();
    }
    
    /**
     * Updates (increments or decrements) the indexes of the first cards of the 
     *      suits.
     * The index is updated if it is higher than the index of the removed or 
     *      added card.
     * @param increment boolean - true if the indexes should increment,
     *                      otherwise false
     * @param cardSuitIndex - index of the removed/added card
     */
    private void updateSuitIndexes(boolean increment, int cardSuitIndex) {
        for (Suit s : Suit.values()) {
            //index of the first card in the suit
            int ind = indexOfSuits.get(s);
            if (ind > cardSuitIndex) {                    
                if(increment) {
                    indexOfSuits.put(s, ind++);
                } else {
                    indexOfSuits.put(s, ind--);
                }
            }
        }
    }
    
    /**
     * Gets all cards in the hand as a collection. Does not remove.
     * @return cards in the hand
     */
    public Collection<? extends Card> getCardsInTheHand() {
        return cardsInTheHand;
    }
    
    /**
     * Retruns true if a card of the specified rank is contained among the cards
     * in the hand
     * @param rank rank to look for
     * @return false if a card of the specified rank is not contained
     */
    public boolean containsRank(Rank rank) {
        for(Suit suit : Suit.values()) {
            if ( cardsInTheHand.contains(new Card(suit,rank)) ) {
                return true;
            }
        }        
        return false;
    }
}
